package org.dromara.common.doc.handler;

import cn.hutool.core.io.IoUtil;
import io.swagger.v3.core.jackson.TypeNameResolver;
import io.swagger.v3.core.util.AnnotationsUtils;
import io.swagger.v3.oas.annotations.tags.Tags;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.core.providers.JavadocProvider;
import org.springdoc.core.service.OpenAPIService;
import org.springdoc.core.service.SecurityService;
import org.springdoc.core.utils.PropertyResolverUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.method.HandlerMethod;

import java.io.StringReader;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;


/**
 * @Author 刘武贵
 * @Date 2024/8/15 22:00
 * @Description 功能描述：自定义 openapi 处理器
 * 对源码功能进行修改 增强使用
 */
@Slf4j
@SuppressWarnings("all")
public class OpenApiHandler extends OpenAPIService {

    /**
     * The Basic error controller.
     * 声明一个静态私有变量basicErrorController，类型为Class<?>。
     * 该变量用于存储基本错误控制器的类类型，以便在需要时直接使用。
     * 使用基本错误控制器可以统一处理应用中的错误返回，提高代码的复用性和一致性。
     */
    private static Class<?> basicErrorController;

    /**
     * The Security parser.
     * 安全服务解析器，用于处理与安全相关的操作
     */
    private final SecurityService securityParser;

    /**
     * The Mappings map.
     * 定义一个HashMap用于存储mappings，键为字符串，值可以是任意对象
     */
    private final Map<String, Object> mappingsMap = new HashMap<>();

    /**
     * The Springdoc tags.
     * 存储HandlerMethod与Tag的映射关系，用于自动生成API文档时的标签管理
     */
    private final Map<HandlerMethod, Tag> springdocTags = new HashMap<>();

    /**
     * The Open api builder customisers.
     * 用于存储自定义OpenAPI构建器的可选列表
     * 这些定制器允许在OpenAPI文档生成过程中进行定制和扩展
     */
    private final Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers;

    /**
     * The server base URL customisers.
     * 服务器基础URL定制器的可选列表
     * 该字段用于存储可能用于定制服务器基础URL的定制器列表
     * 由于使用了Optional，因此该列表可能为空，表示没有定制器被提供
     */
    private final Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers;

    /**
     * The Spring doc config properties.
     * SpringDoc配置属性，用于配置和管理API文档生成的相关选项
     */
    private final SpringDocConfigProperties springDocConfigProperties;

    /**
     * The Cached open api map.
     * 缓存已加载的OpenAPI实例，用于避免重复加载同一个API定义
     */
    private final Map<String, OpenAPI> cachedOpenAPI = new HashMap<>();

    /**
     * The Property resolver utils.
     * PropertyResolverUtils工具类的实例，用于处理和解析属性。
     */
    private final PropertyResolverUtils propertyResolverUtils;

    /**
     * The javadoc provider.
     * javadocProvider变量用于存储可选的Javadoc提供者，该提供者负责生成或提供Java文档注释。
     * 使用Optional容器类表示该字段可能为空，即Javadoc提供者可能未被实现或指定。
     */
    private final Optional<JavadocProvider> javadocProvider;

    /**
     * The Context.
     * ApplicationContext 类型的引用，用于获取 Spring 容器中的 Bean
     */
    private ApplicationContext context;

    /**
     * The Open api.
     * 定义一个OpenAPI对象，用于处理与开放API相关的操作
     */
    private OpenAPI openAPI;

    /**
     * The Is servers present.
     * 标记是否检测到服务器
     */
    private boolean isServersPresent;

    /**
     * The Server base url.
     * 服务器基础URL地址，用于构建完整的请求URL
     */
    private String serverBaseUrl;

    /**
     * OpenApiHandler 构造函数
     *
     * @param openAPI OpenAPI 定义的可选对象
     * @param securityParser 用于解析安全定义的服务
     * @param springDocConfigProperties SpringDoc 配置属性
     * @param propertyResolverUtils 属性解析工具
     * @param openApiBuilderCustomizers 自定义 OpenApi 构建器的可选列表
     * @param serverBaseUrlCustomizers 自定义服务器基础 URL 的可选列表
     * @param javadocProvider Javadoc 提供者
     */
    public OpenApiHandler(Optional<OpenAPI> openAPI, SecurityService securityParser,
                          SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
                          Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
                          Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
                          Optional<JavadocProvider> javadocProvider) {
        // 调用父类的构造函数，传递相关参数
        super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);

        // 如果提供了 OpenAPI 对象，则进行相关初始化
        if (openAPI.isPresent()) {
            this.openAPI = openAPI.get();
            // 确保 OpenAPI 对象中 Components 不为空
            if (this.openAPI.getComponents() == null)
                this.openAPI.setComponents(new Components());
            // 确保 OpenAPI 对象中 Paths 不为空
            if (this.openAPI.getPaths() == null)
                this.openAPI.setPaths(new Paths());
            // 标记是否存在服务器定义
            if (!CollectionUtils.isEmpty(this.openAPI.getServers()))
                this.isServersPresent = true;
        }

        // 初始化成员变量
        this.propertyResolverUtils = propertyResolverUtils;
        this.securityParser = securityParser;
        this.springDocConfigProperties = springDocConfigProperties;
        this.openApiBuilderCustomisers = openApiBuilderCustomizers;
        this.serverBaseUrlCustomizers = serverBaseUrlCustomizers;
        this.javadocProvider = javadocProvider;

        // 根据配置决定是否使用完全限定名
        if (springDocConfigProperties.isUseFqn())
            TypeNameResolver.std.setUseFqn(true);
    }

    /**
     * 根据处理器方法构建标签
     *
     * 本方法旨在根据给定的处理器方法（HandlerMethod），构建和更新OpenAPI中的标签（Tag）
     * 它会从处理器方法本身及其所属类中提取标签，并考虑Springdoc自定义标签以及安全需求
     *
     * @param handlerMethod 处理器方法对象，包含方法和其所属类的信息
     * @param operation OpenAPI操作对象，用于描述API操作
     * @param openAPI OpenAPI对象，用于存储和描述API的全局信息
     * @param locale 当前操作的区域设置，用于国际化
     * @return 更新后的Operation对象，包含新增或更新的标签信息
     */
    @Override
    public Operation buildTags(HandlerMethod handlerMethod, Operation operation, OpenAPI openAPI, Locale locale) {

        // 初始化标签集合，用于存储Tag对象
        Set<Tag> tags = new HashSet<>();
        // 初始化字符串集合，用于存储标签的名称
        Set<String> tagsStr = new HashSet<>();

        // 从处理器方法中构建标签
        buildTagsFromMethod(handlerMethod.getMethod(), tags, tagsStr, locale);
        // 从处理器方法所属类中构建标签
        buildTagsFromClass(handlerMethod.getBeanType(), tags, tagsStr, locale);

        // 如果标签字符串集合不为空，则对每个字符串进行本地化处理
        if (!CollectionUtils.isEmpty(tagsStr))
            tagsStr = tagsStr.stream()
                .map(str -> propertyResolverUtils.resolve(str, locale))
                .collect(Collectors.toSet());

        // 如果处理器方法已关联Springdoc自定义标签，则添加到标签字符串集合中
        if (springdocTags.containsKey(handlerMethod)) {
            io.swagger.v3.oas.models.tags.Tag tag = springdocTags.get(handlerMethod);
            tagsStr.add(tag.getName());
            // 如果OpenAPI中的标签集合不包含此标签，则添加
            if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
                openAPI.addTagsItem(tag);
            }
        }

        // 如果标签字符串集合不为空，则更新操作的标签
        if (!CollectionUtils.isEmpty(tagsStr)) {
            // 如果操作尚未关联任何标签，则新建标签列表
            if (CollectionUtils.isEmpty(operation.getTags()))
                operation.setTags(new ArrayList<>(tagsStr));
            else {
                // 否则，合并现有标签和新标签字符串集合
                Set<String> operationTagsSet = new HashSet<>(operation.getTags());
                operationTagsSet.addAll(tagsStr);
                operation.getTags().clear();
                operation.getTags().addAll(operationTagsSet);
            }
        }

        // 如果配置为自动为类生成标签，并且操作需要标签，则执行以下操作
        if (isAutoTagClasses(operation)) {

            // 如果存在Javadoc提供者，尝试使用Javadoc作为标签信息
            if (javadocProvider.isPresent()) {
                String description = javadocProvider.get().getClassJavadoc(handlerMethod.getBeanType());
                if (StringUtils.isNotBlank(description)) {
                    io.swagger.v3.oas.models.tags.Tag tag = new io.swagger.v3.oas.models.tags.Tag();

                    // 自定义部分 修改使用java注释当tag名
                    List<String> list = IoUtil.readLines(new StringReader(description), new ArrayList<>());
                    // tag.setName(tagAutoName);
                    tag.setName(list.get(0));
                    operation.addTagsItem(list.get(0));

                    tag.setDescription(description);
                    // 如果OpenAPI中的标签集合不包含此标签，则添加
                    if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
                        openAPI.addTagsItem(tag);
                    }
                }
            } else {
                // 如果不存在Javadoc提供者，则使用处理器方法所属类的简化名称作为标签
                String tagAutoName = splitCamelCase(handlerMethod.getBeanType().getSimpleName());
                operation.addTagsItem(tagAutoName);
            }
        }

        // 如果标签集合不为空，则更新OpenAPI的标签列表
        if (!CollectionUtils.isEmpty(tags)) {
            // Existing tags 现有标签
            List<io.swagger.v3.oas.models.tags.Tag> openApiTags = openAPI.getTags();
            if (!CollectionUtils.isEmpty(openApiTags))
                tags.addAll(openApiTags);
            openAPI.setTags(new ArrayList<>(tags));
        }

        // Handle SecurityRequirement at operation level
        // 处理操作级别的安全需求
        io.swagger.v3.oas.annotations.security.SecurityRequirement[] securityRequirements = securityParser
            .getSecurityRequirements(handlerMethod);
        if (securityRequirements != null) {
            // 根据安全需求更新操作的安全配置
            if (securityRequirements.length == 0)
                operation.setSecurity(Collections.emptyList());
            else
                securityParser.buildSecurityRequirement(securityRequirements, operation);
        }

        // 返回更新后的操作对象
        return operation;
    }

    /**
     * 从方法注解中构建标签集合
     *
     * @param method 要解析的方法对象
     * @param tags   用于存储构建的Tag对象的集合
     * @param tagsStr 用于存储方法标签的字符串集合
     * @param locale  用于解析标签名称的区域设置
     */
    private void buildTagsFromMethod(Method method, Set<io.swagger.v3.oas.models.tags.Tag> tags, Set<String> tagsStr, Locale locale) {
        // method tags
        // 检索并合并与方法相关的所有Tags和Tag注解
        Set<Tags> tagsSet = AnnotatedElementUtils
            .findAllMergedAnnotations(method, Tags.class);
        Set<io.swagger.v3.oas.annotations.tags.Tag> methodTags = tagsSet.stream()
            .flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
        methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));

        // 如果方法标签集合不为空，则处理这些标签
        if (!CollectionUtils.isEmpty(methodTags)) {
            // 解析标签名称并添加到字符串集合中
            tagsStr.addAll(StreamUtils.toSet(methodTags, tag -> propertyResolverUtils.resolve(tag.name(), locale)));
            // 将标签添加到Tag对象集合中
            List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
            addTags(allTags, tags, locale);
        }
    }

    /**
     * 将注解中的标签信息添加到OpenAPI文档的标签集合中
     * 此方法解析源代码中的标签注解，提取标签名称和描述，并将其添加到OpenAPI文档的标签集合中
     * 如果存在相同名称的标签，则不会重复添加
     *
     * @param sourceTags 标签注解的列表，来自源代码
     * @param tags OpenAPI文档中标签集合的引用，用于添加解析后的标签
     * @param locale 用于解析标签名称和描述的本地化设置
     */
    private void addTags(List<io.swagger.v3.oas.annotations.tags.Tag> sourceTags, Set<io.swagger.v3.oas.models.tags.Tag> tags, Locale locale) {
        // 使用AnnotationsUtils工具类解析源代码中的标签注解
        Optional<Set<io.swagger.v3.oas.models.tags.Tag>> optionalTagSet = AnnotationsUtils
            .getTags(sourceTags.toArray(new io.swagger.v3.oas.annotations.tags.Tag[0]), true);
        // 如果解析到的标签集合非空，则进行处理
        optionalTagSet.ifPresent(tagsSet -> {
            // 遍历解析后的标签集合
            tagsSet.forEach(tag -> {
                // 解析标签的名称和描述，并应用本地化设置
                tag.name(propertyResolverUtils.resolve(tag.getName(), locale));
                tag.description(propertyResolverUtils.resolve(tag.getDescription(), locale));
                // 检查是否已存在相同名称的标签，如果不存在，则添加到OpenAPI文档的标签集合中
                if (tags.stream().noneMatch(t -> t.getName().equals(tag.getName())))
                    tags.add(tag);
            });
        });
    }

}
